import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

class PanelTreeTable extends JPanel implements TreeSelectionListener {
	
	// all data
	private Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys;
	
	// status panel
	private StatusPanel statusBar;
	
	// table model
	private MemViewerTM memViewerTM; 
	private Node topNode;
	private DefaultMutableTreeNode rootNode;
	private DefaultMutableTreeNode currentSelectedNode;
	// private Object nodeInfo;
	
	// tree view
    private JTree tree;
    private JTable table;

    //Optionally play with line styles.  Possible values are
    //"Angled" (the default), "Horizontal", and "None".
    private static boolean playWithLineStyle = false;
    private static String lineStyle = "Horizontal";
    
    // last saving directory
    //private File currentDir;
    
    // popup menu to save the group you selected
    private JPopupMenu popUp;
    
    // search queue
    private Queue<DefaultMutableTreeNode> searchQueue;
    
    ActionListener popUpL = new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
	    	
	    	String menuTitle = ((JMenuItem)e.getSource()).getText();
	    	
	    	if(menuTitle.equals("Save sub list")) {
	    		subSave(false);
	    	}
	    	
	    	if(menuTitle.equals("Save full sub list")) {
	    		subSave(true);
	    	}

	    }
	};
	
	KeyListener keyL = new KeyListener() {
     	public void keyPressed(KeyEvent e) {
     		
     		if (e.getKeyCode() == KeyEvent.VK_S && e.isControlDown()) {
     			subSave(false);
     		}

     		if (e.getKeyCode() == KeyEvent.VK_D && e.isControlDown()) {
     			if ( MemViewer.DEBUG )
     				MemViewer.DEBUG = false;
     			else
     				MemViewer.DEBUG = true;
     		}
     	}
     	
     	public void keyTyped(KeyEvent e) { }
     	
     	public void keyReleased(KeyEvent e) { }		
	};
	
	private int calcDecrease(char c) {
		
		// calculate the decrease
		int lDecrease = 48;

		switch(c) {
            case '1':
            	// do nothing
            	break;
            case '2':
            	lDecrease += 1;
            	break;
            case '3':
            	lDecrease += 2;
            	break;
            case '4':
            	lDecrease += 3;
            	break;
		}
		
		return lDecrease;
	}

	private String generateLine(Node node, int lDecrease) {

		String line = (node.getLevel() - lDecrease ) + "," + 
						node.getNodeTitle() + "," +
						node.getSizeRom()   + "," + 
						node.getSizeRam();
		
		return line;
	}

	private void subSave(boolean full) {
		
    	// no node was selected, do nothing
    	if ( null == currentSelectedNode ) { return ; }
    	
    	// not load yet
    	if ( null == allSys ) { return; }
    	
    	// root node selected, do nothing
        Object nodeInfo = currentSelectedNode.getUserObject();
    	Node selectedNode = (Node)nodeInfo;

    	if (MemViewer.DEBUG) System.out.println(selectedNode);
    	
		// save file dialog, the filter is for .csv file only and set the default file name
		JFileChooser fileChooser = new JFileChooser(".");
		if ( null !=  MemViewer.currentDir ) fileChooser.setCurrentDirectory(MemViewer.currentDir);
		FileNameExtensionFilter filter = new FileNameExtensionFilter(".csv files", "csv");
		fileChooser.setFileFilter(filter);
		File selectedFile = new File(selectedNode.getNodeTitle() + ".csv");
		if ( full ) { selectedFile = new File(selectedNode.getNodeTitle() + "_full.csv"); }
		fileChooser.setSelectedFile(selectedFile);
		int rVal = fileChooser.showSaveDialog(PanelTreeTable.this);
		if(rVal == JFileChooser.APPROVE_OPTION) {
        
			// recored the current directory
			MemViewer.currentDir = fileChooser.getCurrentDirectory();
			
			// calculate the decrease for level
			int lDecrease = calcDecrease(selectedNode.getLevel());
			
			Queue<Node> saveQueue = new LinkedList<Node>();
			
			getAllNodesFromNode(saveQueue, currentSelectedNode, full);
			
    		try {
    			PrintWriter out = new PrintWriter(fileChooser.getSelectedFile());
    			try {
    				while( saveQueue.peek() != null ) {
    					String line;
		        		line = generateLine(saveQueue.remove(), lDecrease);
			        	out.println(line);
    				}
    			} finally {
    				out.close();
    			}
    		} catch(IOException ioe) {
    			throw new RuntimeException(ioe);
    		}
		}
	}

	private void getAllNodesFromNode(Queue<Node> queue, DefaultMutableTreeNode node, boolean full) {
		
		queue.offer((Node)(node.getUserObject()));

		if (node.getChildCount() >= 0) {
			for (Enumeration e=node.children(); e.hasMoreElements(); ) {
				DefaultMutableTreeNode n = (DefaultMutableTreeNode)e.nextElement();
				if ( full ) { 
					getAllNodesFromNode(queue, n, full);
				} else {
					queue.offer((Node)(n.getUserObject()));
				}
		    }
		}

	}
	
	private void getAllNodesWithStr	(Queue<DefaultMutableTreeNode> queue,
			DefaultMutableTreeNode node, String inputStr) {
		Object nodeInfo = node.getUserObject();
		Node selectedNode = (Node)nodeInfo;
		String nodeTitle = selectedNode.getNodeTitle();
		if ( nodeTitle.indexOf(inputStr) != -1 ) {
			queue.offer(node);
			if (MemViewer.DEBUG) System.out.println("search found -> " + selectedNode);

		} else {
		   
			if (node.getChildCount() >= 0) {
				for (Enumeration e=node.children(); e.hasMoreElements(); ) {
					DefaultMutableTreeNode n = (DefaultMutableTreeNode)e.nextElement();
					getAllNodesWithStr(queue, n, inputStr);
			    }
			}
		}
	}

	private void searchNextFromQ() {
		
    	if ( null == searchQueue ) return;
    	
		if ( searchQueue.peek() != null ) {
			currentSelectedNode = searchQueue.remove();
		} else {
			return;
		}
		
	    // get the tree path with the current node
	    TreePath nodePath = new TreePath(((DefaultTreeModel)tree.getModel()).getPathToRoot(currentSelectedNode));
	    
	    if ( MemViewer.DEBUG ) System.out.println("path is : " + nodePath);
	    
	    // select the node with the path
	    tree.setSelectionPath(nodePath);
	    tree.scrollPathToVisible(nodePath);
		
		setTableWithCurrentNode(currentSelectedNode);		
	}
	
	public void searchAllNodesWithStr(String inputStr) {
		
		if ( null == inputStr || null == rootNode ) return;
		
		searchQueue = new LinkedList<DefaultMutableTreeNode>();
		getAllNodesWithStr(searchQueue, rootNode, inputStr);

		searchNextFromQ();

	}

    public void searchNext() {
    	searchNextFromQ();
    }
	
    public int getTopNodeSizeRam() {
    	return topNode.getSizeRam();
    }
    
    public int getTopNodeSizeRom() {
    	return topNode.getSizeRom();
    }
    
    public void setTopNodeSizeRam(int sizeRam) {
    	topNode.setSizeRam(sizeRam);
    }
    
    public void setTopNodeSizeRom(int sizeRom) {
    	topNode.setSizeRom(sizeRom);
    }
        
    public PanelTreeTable() {
        super(new GridLayout(1,0));
        
        allSys = null;
        statusBar = null;
        currentSelectedNode = null;
        searchQueue = null;
        
        panelLayout();
    }

    public PanelTreeTable(StatusPanel sb) {
        super(new GridLayout(1,0));
        
        allSys = null;
        statusBar = sb;
        currentSelectedNode = null;
        searchQueue = null;
               
        panelLayout();
    }    
    
    public PanelTreeTable(Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys) {
        super(new GridLayout(1,0));
        
        this.allSys = allSys;
        statusBar = null;
        currentSelectedNode = null;
        searchQueue = null;
        
        panelLayout();
    }
    
    public void setData(Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys) {
    	
    	// transfer the data
    	this.allSys = allSys;
    	
    	// Remove all nodes except the root node
    	rootNode.removeAllChildren();
    	
    	// create nodes with the new data
    	createNodes(rootNode);
    	
    	// reload the tree
    	reload();
    }
    
    /** reload the tree */
    private void reload() {
    	((DefaultTreeModel)tree.getModel()).reload();
    }

	private void showError(String title, String message) {
		JOptionPane.showMessageDialog(null,
				message, title,
                JOptionPane.ERROR_MESSAGE);
	}
 
	private void setStatusBar(Node selectedNode) {
		if ( null != statusBar && null != selectedNode ) {
			Integer sra = selectedNode.getSizeRam();
	        Integer sro = selectedNode.getSizeRom();
	        String nodeTitle = selectedNode.getNodeTitle();
	        if (nodeTitle.equals("Group")) {
	        	statusBar.setNodeTitle("Total");
	        } else {
	        	statusBar.setNodeTitle(selectedNode.getNodeTitle());
	        }
			statusBar.setSizeRom(sro.toString());
			statusBar.setSizeRam(sra.toString());
		} 
	}

	private void addRowData(ArrayList<Object> tableData, Node subNode) {
		if ( null != tableData && null != subNode ) {
	    	ArrayList<Object> rowData = new ArrayList<Object>();
	    	rowData.add(subNode.getNodeTitle());
	    	rowData.add(subNode.getSizeRam());
	    	rowData.add(subNode.getSizeRom());
	    	tableData.add(rowData);
		}
	}
	
	private void setTableData(ArrayList<Object> tableData) {
		if ( null != tableData && null != memViewerTM ) {
	        memViewerTM.setData(tableData);
	        memViewerTM.fireTableChanged(new TableModelEvent(memViewerTM));
		}
	}
	
	private void setTableWithCurrentNode_bak(DefaultMutableTreeNode currentSelectedNode) {
    	
        if ( null == currentSelectedNode) return;

        // if no data has been loaded, return
        if ( null == allSys ) return;
        
        // set the table sortable
        table.setAutoCreateRowSorter(true);
        
        // get the node info you have selected
        Object nodeInfo = currentSelectedNode.getUserObject();
		Node selectedNode = (Node)nodeInfo;
    	ArrayList<Object> tableData = new ArrayList<Object>();
        if (currentSelectedNode.isRoot()) {
	        for(Node subNode : allSys.keySet()) {
	        	addRowData(tableData, subNode);
			}
	        setTableData(tableData);
	        setStatusBar(selectedNode);
        } else if (currentSelectedNode.isLeaf()) {
        	addRowData(tableData, selectedNode);
        	setTableData(tableData);
	        setStatusBar(selectedNode);
        } else {
	        if ( null != selectedNode.getObject() ) {
	        	Node sys = selectedNode.getSystem();
	        	Node mod = selectedNode.getModule();   	
		        for(Node subNode : allSys.get(sys).get(mod).get(selectedNode).keySet()) {
		        	addRowData(tableData, subNode);
				}
		        setTableData(tableData);
		        setStatusBar(selectedNode);
	        } else if ( null != selectedNode.getModule() ) {
	        	Node sys = selectedNode.getSystem();   	
		        for(Node subNode : allSys.get(sys).get(selectedNode).keySet()) {
		        	addRowData(tableData, subNode);
				}
		        setTableData(tableData);	  
		        setStatusBar(selectedNode);
	        } else if ( null != selectedNode.getSystem() ) {
		        for(Node subNode : allSys.get(selectedNode).keySet()) {
		        	addRowData(tableData, subNode);
				}
		        setTableData(tableData);
		        setStatusBar(selectedNode);
	        } else {
        		String title = "Node Error!";
        		String message = "you should never be here!";
        		showError(title, message);
        		return;
	        }

        }
        
        if (MemViewer.DEBUG) System.out.println(nodeInfo.toString());
	}
	
	private void setTableWithCurrentNode(DefaultMutableTreeNode currentSelectedNode) {
    	
        if ( null == currentSelectedNode) return;

        // if no data has been loaded, return
        if ( null == allSys ) return;
        
        // set the table sortable
        table.setAutoCreateRowSorter(true);
        
        // get the node info you have selected
        Object nodeInfo = currentSelectedNode.getUserObject();
		Node selectedNode = (Node)nodeInfo;

        // table data
    	ArrayList<Object> tableData = new ArrayList<Object>();
 
		if (currentSelectedNode.getChildCount() > 0) {
			for (Enumeration e=currentSelectedNode.children(); e.hasMoreElements(); ) {
				DefaultMutableTreeNode n = (DefaultMutableTreeNode)e.nextElement();
				addRowData(tableData, (Node)n.getUserObject());
		    }
			setTableData(tableData);
		} else {
        	addRowData(tableData, selectedNode);
        	setTableData(tableData);
		}
		
        setStatusBar(selectedNode);
        
        if (MemViewer.DEBUG) System.out.println(nodeInfo.toString());
	}
	
    /** Required by TreeSelectionListener interface. */
    public void valueChanged(TreeSelectionEvent e) {
    	
		// get the current node to see if it's null
    	currentSelectedNode = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
        if ( null == currentSelectedNode) return;
    	
    	setTableWithCurrentNode(currentSelectedNode);
    }
    
    class PopupListener extends MouseAdapter {
	    public void mousePressed(MouseEvent e) {
	    	maybeShowPopup(e);
	    }
	    public void mouseReleased(MouseEvent e) {
	    	maybeShowPopup(e);
	    }
	    private void maybeShowPopup(MouseEvent e) {
	    	
	    	// no node was selected, do nothing
	    	if ( null == currentSelectedNode ) { return ; }
	    	
	    	if(e.isPopupTrigger())
	    		popUp.show(e.getComponent(), e.getX(), e.getY());
	    }
	}
    
    private void panelLayout() {
    	   	
        // popup menu
        popUp = new JPopupMenu();
        JMenuItem m = new JMenuItem("Save sub list");
        m.addActionListener(popUpL);
        popUp.add(m);
        m = new JMenuItem("Save full sub list");
        m.addActionListener(popUpL);
        popUp.add(m);
    	
        //Create the root node.
    	topNode = new Node();
    	topNode.setNodeTitle("Group");
    	//topNode.setLevel('0');
    	rootNode = new DefaultMutableTreeNode(topNode);
    	createNodes(rootNode);
        
        //Create a tree that allows one selection at a time.
        tree = new JTree(new DefaultTreeModel(rootNode));
        tree.getSelectionModel().setSelectionMode
                (TreeSelectionModel.SINGLE_TREE_SELECTION);

        // Listen for when the selection changes.
        tree.addTreeSelectionListener(this);
        // add the pop up menu listener
        tree.addMouseListener(new PopupListener());
        // add the key listener 
        tree.addKeyListener(keyL);
                
        if (playWithLineStyle) {
            System.out.println("line style = " + lineStyle);
            tree.putClientProperty("JTree.lineStyle", lineStyle);
        }

        //Create the scroll pane and add the tree to it. 
        JScrollPane treeView = new JScrollPane(tree);

        //Create the table.
        //Create the scroll pane and add the table to it.
        memViewerTM = new MemViewerTM();
        table = new JTable(memViewerTM);
        // add the key listener 
        table.addKeyListener(keyL);
        JScrollPane tableView = new JScrollPane(table);
        
        //Add the scroll panes to a split pane.
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.setLeftComponent(treeView);
        splitPane.setRightComponent(tableView);

        Dimension minimumSize = new Dimension(100, 50);
        tableView.setMinimumSize(minimumSize);
        treeView.setMinimumSize(minimumSize);
        splitPane.setDividerLocation(200); 
        splitPane.setPreferredSize(new Dimension(100, 100));
        
        // Add the split pane to this panel.
        add(splitPane);
        
        //add(BorderLayout.SOUTH, statusBar);
        
        // it's not working to add the KeyListener to the panel, I guess no focus can get
        // it's not working to add the KeyListener to the frame, either. Same reason I guess
        //addKeyListener(new KeyL());

    }
    
    private void createNodes(DefaultMutableTreeNode top) {
        DefaultMutableTreeNode lastSysNode = null;
        DefaultMutableTreeNode lastModNode = null;
        DefaultMutableTreeNode lastObjNode = null;
        DefaultMutableTreeNode lastSecNode = null;

        if ( ( allSys != null ) && (! allSys.isEmpty()) ) {
    		for(Node sys : allSys.keySet()) {
    			int sizeRom = sys.getSizeRom();
    			int sizeRam = sys.getSizeRam();
    			
    			// no need to add the empty system
    			if ( 0 == sizeRom && 0 == sizeRam ) {
    				continue;
    			}
    			
    			lastSysNode = new DefaultMutableTreeNode(sys);
    			top.add(lastSysNode);
    			for(Node mod : allSys.get(sys).keySet()) {
    				lastModNode = new DefaultMutableTreeNode(mod);
    				lastSysNode.add(lastModNode);
    				for(Node obj : allSys.get(sys).get(mod).keySet()) {
        				lastObjNode =  new DefaultMutableTreeNode(obj);
        				lastModNode.add(lastObjNode);
        				for(Node sec : allSys.get(sys).get(mod).get(obj).keySet()) {
        					lastSecNode =  new DefaultMutableTreeNode(sec);
        					lastObjNode.add(lastSecNode);
        				}
    				}
    			}
    		}
        }
        
        return;
    }
}